﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Objects.Tags;
using Objects.Factories;
using System.Xml.Schema;
using System.Reflection;
using System.Diagnostics;
using System.IO;

namespace Objects
{
    /// <summary>
    /// Фабрика объектов домена приложения
    /// </summary>
    public class XmlFactory : BaseFactory<XName, XElement, Entity>
    {
        static XmlFactory()
        {
            DefaultTags<Type, XNamespace>.GetDefaultFunc = (type) => XNamespace.None;
            CXName.Init();
            Valuehost.Init();
            Commands.Add("Guid.New", (s) => Guid.NewGuid());
            Commands.Add("Registry.Get", (s) =>
                new LazyObject<object>(() =>
                Registry.Instance[(Guid)s.Attributes().First()]));
            Commands.Add("Date.Now", (s) => DateTime.Now.Date);
            Commands.Add("DateTime.Now", (s) => DateTime.Now);
        }
        public static void Load(string uriName)
        {
            Load(XElement.Load(uriName));
        }

        public static void Load(XElement xe)
        {
            foreach (var item in xe.Elements())
                factories.Add(item.Name, new XmlFactory(item));
        }

        private class DictionaryIndexAccess<Tindx, Tvalue> : IIndexAccess<Tindx, Tvalue>
        {
            public bool ReadOnly;
            Dictionary<Tindx, Tvalue> dict;
            public DictionaryIndexAccess(Dictionary<Tindx, Tvalue> dict, bool ReadOnly)
            {
                this.dict = dict;
                this.ReadOnly = ReadOnly;
            }
            #region IndexAccess<Tindx,Tvalue> Members

            public Tvalue this[Tindx index]
            {
                get
                {
                    return dict.GetOrDefault(index, default(Tvalue));
                }
                set
                {
                    if (!ReadOnly)
                        dict.AddOrSetValue(index, value);
                }
            }

            #endregion
        }
        private static readonly Dictionary<XName, XmlFactory> factories = new Dictionary<XName, XmlFactory>();
        public static readonly IIndexAccess<XName, XmlFactory> Factory = new DictionaryIndexAccess<XName, XmlFactory>(factories, true);


        public static XmlFactory Default
        {
            get
            {
                return factories.FirstOrDefault().Value;
            }
        }

        protected ValuedFactory FactoryValueProp = new ValuedFactory();

        public T Build<T>()
            where T : class
        {
            return Build<T>(CXName.Get(typeof(T)));
        }

        public Carry<Entity, XName> Make(XElement xe)
        {
            return (xe != null) ? Make(xe.Name, xe) : null;
        }

        private object BuildWithContext2(object context, XElement item)
        {
            string lname;
            string mname;
            PropertyInfo pi;
            MethodInfo mi;
            lname = item.Name.LocalName;
            if (lname.StartsWith("sys."))
                return Command(item);
            else if (!lname.StartsWith("this."))
                return Make(item).Content;
            else if (lname.Length > 5)
                if (context != null)
                {
                    if (lname[5] != '-')
                        if ((pi = context.GetType().GetProperty(lname.Substring(5))) != null)
                            return pi.GetValue(context, nullprms);
                        else
                            Debug.WriteLine("Не найдено свойство {0}".goFormat(lname));
                    else
                    {
                        mname = lname.Substring(6);
                        if ((mi = context.GetType().GetMethods().Where(s => s.Name == mname).FirstOrDefault()) != null)
                        {
                            object[] parms = nullprms;
                            if (item.Nodes().Any())
                            {
                                List<object> prms = new List<object>();
                                object tmpo;
                                XElement xe; XText xt;
                                foreach (var xnode in item.Nodes())
                                    if ((xe = xnode as XElement) != null)
                                    {
                                        if ((tmpo = BuildWithContext(context, xe)) != null)
                                            prms.Add(tmpo);
                                    }
                                    else if ((xt = xnode as XText) != null)
                                        prms.Add(xt.Value);
                                if (prms.Count > 0)
                                    parms = prms.ToArray();
                            }
                            if (mi.ReturnType.Name != "Void")
                                return mi.Invoke(context, parms);
                            else
                                mi.Invoke(context, parms);
                        }
                        else
                            Debug.WriteLine("Не найден метод {0}".goFormat(mname));
                    }
                }
            return null;
        }

        public readonly static Dictionary<string, Func<XElement, object>> Commands
            = new Dictionary<string, Func<XElement, object>>();

        private object Command(XElement item)
        {
            Func<XElement, object> cmdFunc;
            string cmd = item.Name.LocalName;
            if (Commands.TryGetValue(cmd.Substring(cmd.IndexOf('.') + 1), out cmdFunc))
                return cmdFunc(item);
            else
                return null;
        }

        public object BuildWithContext(object context, XElement item)
        {
            Carry<Entity, XName> carry = context as Carry<Entity, XName>;
            if (carry != null)
                return BuildWithContext2(carry.Content, item);
            else
                return BuildWithContext2(context, item);
        }

        public IEnumerable<object> BuildWithContext(object context, IEnumerable<XElement> items)
        {
            object res;
            Carry<Entity, XName> carry = context as Carry<Entity, XName>;
            if (carry != null)
                context = carry.Content;
            foreach (var item in items)
                if ((res = BuildWithContext2(context, item)) != null)
                    yield return res;
        }

        public IEnumerable<Carry<Entity, XName>> MakeFrom(string p)
        {
            if (File.Exists(p))
                foreach (var item in XElement.Load(p).Elements())
                    yield return Make(item);
        }

        public IEnumerable<Carry<Entity, XName>> Make(IEnumerable<XElement> xes)
        {
            foreach (var item in xes)
                yield return Make(item.Name, item);
        }

        public Carry<Entity, XName> Make<T>()
            where T : class
        {
            return Make<T>(CXName.Get(typeof(T)));
        }

        public Carry<Entity, XName> Make<T>(XElement prms)
            where T : class
        {
            return Make(CXName.Get(typeof(T)), prms);
        }

        /// <summary>
        /// Инициализатор фабрики
        /// </summary>
        /// <param name="xe">Xml-инициализатор</param>
        public XmlFactory(XElement xe)
        {
            IsWriteHistoryToCarry = true;
            DefaultPipelinePolicy = PipelinePolicy.Default;
            InitFrom(xe);
        }

        public static object[] nullprms = new object[0];

        protected void InitFrom(XElement xe)
        {
            XElement tmpx;
            if (xe != null)
            {
                bool? tmpb;
                IsWriteHistoryToCarry = !(tmpb = (bool?)xe.Element("IsWriteHistory")).HasValue || tmpb.Value;
                if ((tmpx = xe.Element("DefaultPipelinePolicy")) != null)
                    DefaultPipelinePolicy = (PipelinePolicy)Enum.Parse(typeof(PipelinePolicy), (string)tmpx);
                foreach (var item in xe
                        .Element("Pipelines")
                        .Elements())
                    AddClass(item);
            }
        }

        /// <summary>
        /// Инициализация класса
        /// </summary>
        /// <param name="xmlClass">Xml-инициализатор (на null не проверяется)</param>
        private void AddClass(XElement xmlClass)
        {
            XAttribute xa;
            Type type;
            PropertyInfo pi;
            string tmps;
            type = CXName.GetKeyFromValue(xmlClass.Name).FirstOrDefault();
            if (type == null)
            {
                type = AppDomain.CurrentDomain
                    .GetAssemblies()
                    .SelectMany(s => s.GetTypes())
                    .FirstOrDefault(s => s.Name == xmlClass.Name.LocalName);
                if (type != null)
                    CXName.AddOrSet(type, xmlClass.Name);
            }
            if (type == null)
            {
                xa = xmlClass.Attribute("inherit");
                if (xa != null && !(tmps = (string)xa).IsEmpty())
                {
                    type = AppDomain.CurrentDomain
                        .GetAssemblies()
                        .SelectMany(s => s.GetTypes())
                        .FirstOrDefault(s => s.Name == tmps);
                    if (type != null)
                    {
                        AddStub(xmlClass.Name, SECTION_ENTER_POINT);
                        ForwardSection(xmlClass.Name, SECTION_INHERIT, xmlClass.Name.Namespace + tmps, SECTION_ENTER_POINT);
                    }
                    else
                        return;
                }
            }
            else
            {
                xa = xmlClass.Attribute("abstract");
                if (xa != null && (bool)xa)
                    AddStub(xmlClass.Name, SECTION_ENTER_POINT);
                else
                    AddConstructor(xmlClass.Name, type);
                xa = xmlClass.Attribute("inherit");
                if (xa != null && !(tmps = (string)xa).IsEmpty())
                    ForwardSectionWithSkip(xmlClass.Name, SECTION_INHERIT, xmlClass.Name.Namespace + tmps, SECTION_ENTER_POINT);
            }
            foreach (var fld in xmlClass.Elements())
            {
                pi = type.GetPropertiesWithAttribute<FieldAttribute>().FirstOrDefault(s => s.Name == fld.Name.LocalName);
                if (pi != null)
                    AddField(xmlClass.Name, fld, pi);
                else if (type.GetInterface("IIndexAccess`2") != null)
                    AddContext(xmlClass.Name, fld);
            }
        }


        private void AddContext(XName xClass, XElement xmlField)
        {
            XAttribute xa;
            string fld;
            if (xmlField.Name.LocalName.Contains('.'))
            {
                xa = xmlField.Attribute("type");
                if (xa != null && !(fld = (string)xa).IsEmpty())
                    AddContextField(xClass, xmlField, fld);
            }
            else
                AddContextFields(xClass, xmlField);
        }

        private void AddContextFields(XName xClass, XElement xmlField)
        {
            foreach (var item in xmlField.Elements())
            {
                item.Name = item.Name.Namespace + (xmlField.Name.LocalName + "." + item.Name.LocalName);
                AddContext(xClass, item);
            }
        }

        /// <summary>
        /// Инициализация контекстного свойства класса (не экземпляра)
        /// </summary>
        /// <param name="xClass">Имя класса (на null не проверяется)</param>
        /// <param name="xmlField">Имя свойства (на null не проверяется)</param>
        /// <param name="type">Наименование типа (значение атрибута 'type')</param>
        private void AddContextField(XName xClass, XElement xmlField, string type)
        {
            string cntxNameField = xmlField.Name.LocalName;

            AddAssemblyMethod(xClass, xmlField.Name.LocalName,
            (carry, prms) =>
            {
                IIndexAccess<string, Valuehost> ii = carry.Content as IIndexAccess<string, Valuehost>;
                if (ii != null)
                    ii[cntxNameField] = FactoryValueProp.Make(type, xmlField).Content;
            });
            if (xmlField.Nodes().Any())
            {
                Texthost defaulttext = GetLazyStringP(xmlField, null);
                #region Инициализация stringP
                if (ValuedFactory.IsTextField(type))
                {
                    AddAssemblyMethod(xClass, xmlField.Name.LocalName + "!value",
                        (carry, prms) =>
                        {
                            XElement xe;
                            Texthost strp;
                            IIndexAccess<string, Valuehost> ii = carry.Content as IIndexAccess<string, Valuehost>;
                            if (ii != null)
                            {
                                if (prms != null && (xe = prms.Element(xmlField.Name)) != null)
                                    strp = GetLazyStringP(xe, carry.Content);
                                else
                                    strp = ((Texthost)(defaulttext.Clone())).SetContext(carry.Content);
                                ii[cntxNameField] = strp.InjectValue();
                            }

                        });
                }
                #endregion
                else if ((defaulttext.LazyXml != null && defaulttext.LazyXml.Length > 0) || defaulttext.Format.Length > 0)
                    AddAssemblyMethod(xClass, xmlField.Name.LocalName + "!value",
                            (carry, prms) =>
                            {
                                IIndexAccess<string, Valuehost> ii = carry.Content as IIndexAccess<string, Valuehost>;
                                if (ii != null)
                                    foreach (var item in GetValueMethod(carry, xmlField.Name, prms, defaulttext))
                                        ii[cntxNameField] = item as Valuehost;
                            });
                else
                    AddAssemblyMethod(xClass, xmlField.Name.LocalName + "!value",
                            (carry, prms) =>
                            {
                                IIndexAccess<string, Valuehost> ii = carry.Content as IIndexAccess<string, Valuehost>;
                                if (ii != null)
                                    foreach (var item in GetValueMethod(carry, xmlField.Name, prms, null))
                                        ii[cntxNameField] = item as Valuehost;
                            });
            }
            else
                AddAssemblyMethod(xClass, xmlField.Name.LocalName + "!value",
                            (carry, prms) =>
                            {
                                IIndexAccess<string, Valuehost> ii = carry.Content as IIndexAccess<string, Valuehost>;
                                if (ii != null)
                                    foreach (var item in GetValueMethod(carry, xmlField.Name, prms, null))
                                        ii[cntxNameField] = item as Valuehost;
                            });
        }

        /// <summary>
        /// Инициализация свойства класса (не экземпляра)
        /// </summary>
        /// <param name="xClass">Имя класса (на null не проверяется)</param>
        /// <param name="xmlField">Имя свойства (на null не проверяется)</param>
        /// <param name="pi">Ссылка на свойство (на null не проверяется)</param>
        private void AddField(XName xClass, XElement xmlField, PropertyInfo pi)
        {
            XAttribute xa = xmlField.Attribute("type");
            XName xn = xmlField.Name;
            string fld;
            if (xa != null && !(fld = (string)xa).IsEmpty())
            {
                AddAssemblyMethod(xClass, xmlField.Name.LocalName,
                    (carry, prms) =>
                    {
                        pi.SetValue(carry.Content,
                            FactoryValueProp.Make(fld, xmlField).Content, nullprms);
                    });
                if (xmlField.Nodes().Any())
                {
                    Texthost defaulttext = GetLazyStringP(xmlField, null);
                    #region Инициализация stringP
                    if (ValuedFactory.IsTextField(xa))
                    {
                        AddAssemblyMethod(xClass, xmlField.Name.LocalName + "!value",
                            (carry, prms) =>
                            {
                                XElement xe;
                                Texthost strp;
                                if (prms != null && (xe = prms.Element(xmlField.Name)) != null)
                                    strp = GetLazyStringP(xe, carry.Content);
                                else
                                    strp = ((Texthost)(defaulttext.Clone())).SetContext(carry.Content);
                                pi.SetValue(carry.Content, strp.InjectValue(), nullprms);
                            });
                    }
                    #endregion
                    else if ((defaulttext.LazyXml != null && defaulttext.LazyXml.Length > 0) || defaulttext.Format.Length > 0)
                        AddAssemblyMethod(xClass, xmlField.Name.LocalName + "!value",
                                (carry, prms) =>
                                {
                                    foreach (var item in GetValueMethod(carry, xmlField.Name, prms, defaulttext))
                                        pi.SetValue(carry.Content, item, nullprms);
                                });
                    else
                        AddAssemblyMethod(xClass, xmlField.Name.LocalName + "!value",
                                (carry, prms) =>
                                {
                                    foreach (var item in GetValueMethod(carry, xmlField.Name, prms, null))
                                        pi.SetValue(carry.Content, item, nullprms);
                                });
                }
                else
                    AddAssemblyMethod(xClass, xmlField.Name.LocalName + "!value",
                                (carry, prms) =>
                                {
                                    foreach (var item in GetValueMethod(carry, xmlField.Name, prms, null))
                                        pi.SetValue(carry.Content, item, nullprms);
                                });
                if ((xa = xmlField.Attribute("isKey")) != null && (bool)xa)
                {
                    AddAssemblyMethod(xClass, xmlField.Name.LocalName + "!registry",
                           (carry, prms) =>
                           {
                               Registry.Instance[(Valuehost<Guid>)pi.GetValue(carry.Content, nullprms)] = carry.Content;
                           });
                }
            }
        }

        private IEnumerable<object> GetValueMethod(Carry<Entity, XName> carry, XName xmlNField, XElement prms, Texthost defPrms)
        {
            XElement xe;
            string[] fld;
            if (prms != null &&
                ((xe = prms.Element(xmlNField)) != null
                || ((fld = xmlNField.LocalName.Split('.')).Length == 2
                    && (xe = prms.Element(xmlNField.Namespace + fld[0])) != null
                    && (xe = xe.Element(xmlNField.Namespace + fld[1])) != null)))
                return GetValueMethod(GetLazyStringP(xe, carry.Content));
            else if (defPrms != null)
                return GetValueMethod(((Texthost)(defPrms.Clone())).SetContext(carry.Content));
            else
                return nullprms;
        }

        private IEnumerable<object> GetValueMethod(Texthost prms)
        {
            if (prms != null)
            {
                if (prms.Format.Length > 0)
                    yield return prms.Format.InjectParse();
                if (prms.IsArguments)
                    foreach (var item in prms.Arguments)
                        yield return item.Injection();
            }
        }

        private Texthost GetLazyStringP(XElement xmlField, object context)
        {
            List<XElement> xmls = new List<XElement>();
            StringBuilder sb = new StringBuilder();
            foreach (var item in xmlField.Nodes())
                switch (item.GetType().Name)
                {
                    case "XText":
                        sb.Append(item);
                        break;
                    case "XElement":
                        xmls.Add((XElement)item);
                        break;
                    default:
                        break;
                }
            Texthost res = new Texthost() { Format = sb.ToString().Trim(), DataContext = context };
            if (xmls.Count > 0)
            {
                res.LazyXml = xmls.ToArray();
                res.LazyArguments = (s) =>
                    GetElements(s.DataContext, s.LazyXml);
            }
            return res;
        }

        private IEnumerable<object> GetElements(object context, XElement[] lazyxml)
        {
            if (context != null)
                foreach (var item in BuildWithContext(context, lazyxml))
                    yield return item;
            else if (lazyxml != null)
                foreach (var item in lazyxml)
                    yield return Make(item).Content;
        }

        /// <summary>
        /// Инициализация конструктора
        /// </summary>
        /// <param name="name">Имя класса (на null не проверяется)</param>
        /// <param name="type">Ссылка на тип (на null не проверяется)</param>
        private void AddConstructor(XName name, Type type)
        {
            ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes);
            if (ci != null)
                AddAssemblyMethod(name, SECTION_ENTER_POINT, (carry, prms) =>
                    carry.Content = ci.Invoke(nullprms) as Entity
                );
            else
                AddStub(name, SECTION_ENTER_POINT);
        }

        /// <summary>
        /// Инициализация конструктора
        /// </summary>
        /// <param name="name">Имя класса (на null не проверяется)</param>
        /// <param name="type">Ссылка на тип (на null не проверяется)</param>
        private void AddStub(XName name, string asmName)
        {
            AddAssemblyMethod(name, asmName, null);
        }

        public void AddAssemblyMethod<T>(string asmName, AssemblyMethod assemblyMethod)
            where T : Entity
        {
            AddAssemblyMethod(CXName.Get<T>(), asmName, assemblyMethod);
        }
    }

    public class ValuedFactory : BaseFactory<string, XElement, Valuehost>
    {
        public static object[] nullprms = new object[0];
        public readonly static Dictionary<string, Type> generics = new Dictionary<string, Type>(StringComparer.CurrentCultureIgnoreCase);
        public readonly static Dictionary<string, Type> synonyms = new Dictionary<string, Type>(StringComparer.CurrentCultureIgnoreCase);
        //private Dictionary<string, Func<Valued>> fields = new Dictionary<string, Func<Valued>>();
        static ValuedFactory()
        {
            // Стандартный прототип, без проверки
            generics.Add("value", Assembly.GetAssembly(typeof(Valuehost<int>)).GetTypes().Where(s => s.Name == "Valuehost`1").First());
            // Стандартный прототип с проверкой
            generics.Add("valid", Assembly.GetAssembly(typeof(Valuehost<int>)).GetTypes().Where(s => s.Name == "Validable`1").First());
            // Стандартный прототип-множество
            generics.Add("set", Assembly.GetAssembly(typeof(Valuehost<int>)).GetTypes().Where(s => s.Name == "Collection`1").First());

            synonyms.Add("int", typeof(int));
            synonyms.Add("date", typeof(DateTime));
            synonyms.Add("datetime", typeof(DateTime));
            synonyms.Add("bool", typeof(bool));
            synonyms.Add("time", typeof(DateTime));
            synonyms.Add("string", typeof(string));
            synonyms.Add("text", typeof(Texthost));
        }

        public static bool IsTextField(string type)
        {
            return (!type.IsEmpty() && (type == "text" || type.EndsWith(":text")));
        }

        public static bool IsTextField(XAttribute xa)
        {
            return (xa != null && IsTextField((string)xa));
        }

        public ValuedFactory()
        {
            DefaultPipelinePolicy = PipelinePolicy.StableSection;
            IsWriteHistoryToCarry = false;
        }

        public override Carry<Valuehost, string> Make(string convId, XElement prms)
        {
            Type gentype, type;
            Type[] typeprms;
            string nm;
            ConstructorInfo[] ci;
            Carry<Valuehost, string> crr = null;
            if (!convId.IsEmpty())
            {
                string[] gnrs = convId.Split(':');
                if (!generics.TryGetValue(gnrs[0], out gentype))
                    crr = Make("value:" + convId, prms);
                else
                {
                    ci = new ConstructorInfo[gnrs.Length - 1];
                    nm = gnrs[gnrs.Length - 1];
                    if (!synonyms.TryGetValue(nm, out type))
                        type = AppDomain.CurrentDomain
                            .GetAssemblies()
                            .SelectMany(s => s.GetTypes())
                            .FirstOrDefault(s => s.Name == nm);
                    if (type != null)
                    {
                        typeprms = Type.EmptyTypes;
                        for (int i = gnrs.Length - 2; i >= 0; i--)
                            if (generics.TryGetValue(gnrs[i], out gentype))
                            {
                                type = gentype.MakeGenericType(type);
                                ci[i] = type.GetConstructor(typeprms);
                                if (ci[i] == null)
                                    ci[i] = type.GetConstructor(Type.EmptyTypes);
                                typeprms = new Type[] { type };
                            }
                            else
                                return new Carry<Valuehost, string>()
                                {
                                    Description = "Не найден класс-прототип {0}".goFormat(gnrs[i])
                                };
                        AddAssemblyMethod(convId, SECTION_ENTER_POINT,
                            (carry, parms) =>
                            {
                                object[] objprms = nullprms;
                                object tmp = null;
                                int i = 0;

                                for (i = ci.Length - 1; i >= 0; i--)
                                {
                                    if (ci[i].GetParameters().Length == 0)
                                        tmp = ci[i].Invoke(nullprms);
                                    else
                                        tmp = ci[i].Invoke(objprms);
                                    objprms = new object[] { tmp };
                                }
                                carry.Content = (Valuehost)tmp;
                            }
                            );
                        crr = base.Make(convId, prms);
                    }
                }
            }
            return crr;
        }
    }
}
